﻿package mlc.avm2.visualizing;
import haxe.PosInfos;
import mlc.avm2.visualizing.writetarget.WriteTarget;

/**
 * An pretty printer for XML. It colors the XML, and adds indenting.
 */
class XMLPrettyPrinter {
	private static inline var ATTRIBUTE_VALUE_COLOR:UInt = 0x98ABFB;
	private static inline var CDATA_AND_COMMENT_TAG_COLOR:UInt = 0x89FF89;
	private static inline var CLOSURE_COLOR:UInt = 0xFF7F7F;
	private static inline var NODE_NAME_COLOR:UInt = 0xC97FFF;
	/**
	 * The target this pretty printer will write to.
	 */
	public var target:WriteTarget;
	/**
	 * Creates a new XML pretty printer.
	 */
	public function new():Void {
	}
	private inline function createTabs(numberOfTabs:Int):String {
		return
			switch (numberOfTabs) {
				case 0:
				"";
				case 1:
				"\t";
				case 2:
				"\t\t";
				case 3:
				"\t\t\t";
				default:
				var result:String = "";
				for (i in 0...numberOfTabs) {
					result += "\t";
				}
				result;
			}
	}
	private function determineHasChildren(value:Xml):Bool {
		for (child in value) {
			switch (child.nodeType) {
				case Xml.Element:
				return true;
				case Xml.CData, Xml.Comment, Xml.PCData:
				if (StringTools.ltrim(child.nodeValue).length != 0) {
					return true;
				}
			}
		}
		return false;
	}
	private function writeNode(value:Xml, depth:Int):Void {
		switch (value.nodeType) {
			case Xml.CData:
			target.write(createTabs(depth) + "<", 0xFFFFFF, false);
			target.write("![CDATA[", CDATA_AND_COMMENT_TAG_COLOR, false);
			target.write(StringTools.trim(value.nodeValue), 0xFFFFFF, false);
			target.write("]]", CDATA_AND_COMMENT_TAG_COLOR, false);
			target.write(">", 0xFFFFFF, true);
			case Xml.Comment:
			var commentContent:String = value.nodeValue;
			commentContent = ~/[\n\r\t]+/g.replace(commentContent, "");
			target.write(createTabs(depth), CDATA_AND_COMMENT_TAG_COLOR, false);
			target.write(StringTools.trim(commentContent), CDATA_AND_COMMENT_TAG_COLOR, false);
			target.write("", CDATA_AND_COMMENT_TAG_COLOR, true);
			case Xml.Document:
			for (child in value) {
				writeNode(child, depth);
			}
			case Xml.Element:
			target.write(createTabs(depth) + "<", 0xFFFFFF, false);
			target.write(value.nodeName, NODE_NAME_COLOR, false);
			for (attribute in value.attributes()) {
				target.write(" " + attribute + "=\"", 0xFFFFFF, false);
				target.write(value.get(attribute), ATTRIBUTE_VALUE_COLOR, false);
				target.write("\"", 0xFFFFFF, false);
			}
			if (determineHasChildren(value)) {
				target.write(">", 0xFFFFFF, true);
				for (child in value) {
					writeNode(child, depth + 1);
				}
				target.write(createTabs(depth) + "<", 0xFFFFFF, false);
				target.write("/", CLOSURE_COLOR, false);
				target.write(value.nodeName, NODE_NAME_COLOR, false);
				target.write(">", 0xFFFFFF, true);
			} else {
				target.write(" /", CLOSURE_COLOR, false);
				target.write(">", 0xFFFFFF, true);
			}
			case Xml.PCData:
			var nodeValue:String = StringTools.trim(value.nodeValue);
			if (nodeValue.length != 0) {
				target.write(createTabs(depth) + nodeValue, 0xFFFFFF, true);
			}
		}
	}
	public inline function writeXml(value:Xml):Void {
		writeNode(value, 0);
	}
}